﻿Imports System.Runtime.Serialization

<Serializable()>
Public Class Token
    Implements ISerializable

#Region "Serialization"
    Private Sub New(info As SerializationInfo, context As StreamingContext)
        Dim version As Integer = info.GetInt32("version")

        If version >= 1 Then
            Name = info.GetString("name")
            mArguments = info.GetValue(Of List(Of String))("arguments")
            Comment = info.GetString("comment")
        End If
    End Sub


    Public Sub GetObjectData(info As SerializationInfo, context As StreamingContext) Implements ISerializable.GetObjectData
        info.AddValue("version", 1)

        'Version 1 data
        info.AddValue("name", Name)
        info.AddValue("arguments", mArguments)
        info.AddValue("comment", Comment)
    End Sub
#End Region
    Private mArguments As List(Of String)

    Public Property Name As String
    Public Property Comment As String

    Public Sub New()
        Me.mArguments = New List(Of String)
    End Sub

    Public Sub New(name As String, arguments As IEnumerable(Of String))
        Me.New()

        Me.Name = name
        Me.mArguments.AddRange(arguments)
    End Sub

    Public Sub New(name As String)
        Me.New()

        Me.Name = name
    End Sub

    Public ReadOnly Property HasArguments As Boolean
        Get
            If mArguments Is Nothing OrElse mArguments.Count = 0 Then
                Return False
            Else
                Return True
            End If
        End Get
    End Property

    Public Property ArgumentAsInteger(index As Integer) As Integer
        Get
            Return CInt(mArguments(index))
        End Get
        Set(value As Integer)
            mArguments(index) = value.ToString
        End Set
    End Property

    Public ReadOnly Property ArgumentAsInteger(index As Integer, defaultValue As Integer) As Integer
        Get
            Dim ret As Integer = defaultValue
            If Integer.TryParse(GetArgumentOrDefault(index, "Not a Number"), ret) Then
                Return ret
            Else
                Return defaultValue
            End If
        End Get
    End Property

    Public ReadOnly Property ArgumentsAsString As String
        Get
            If HasArguments Then
                Return String.Join(":"c, mArguments)
            Else
                Return ""
            End If
        End Get
    End Property

    Public Function GetArgumentOrDefault(index As Integer, [default] As String) As String
        If index < mArguments.Count Then
            Return mArguments(index)
        Else
            Return [default]
        End If
    End Function


    Overrides Function ToString() As String
        Return "[" & Name & If(HasArguments, ":" & String.Join(":"c, mArguments), "") & "]"
    End Function

    Public Shared Function Parse(line As String, obj As RawObject) As Token
        Dim ret As New Token
        Dim parts() As String = SplitTokenLine(line)
        If parts IsNot Nothing AndAlso parts.Length > 0 Then
            ret.Name = parts(0)
            If parts.Length > 1 Then
                ret.ReparseArgs(parts(1), obj)
            End If
        End If
        Return ret
    End Function

    Private Shared Function SplitTokenLine(line As String) As String()
        line = line.Trim
        If line.StartsWith("[") And line.EndsWith("]") Then
            Return line.Substring(1, line.Length - 2).Split(New Char() {":"c}, 2)
        Else
            Return Nothing
        End If
    End Function

    Public Function HasArgumentValue(value As String) As Boolean
        For Each arg As String In mArguments
            If arg = value Then
                Return True
            End If
        Next
        Return False
    End Function

    Public Function HasArgumentValue(argIndex As Integer, value As String) As Boolean
        Return CBool(mArguments(argIndex) = value)
    End Function

    Public Sub ReparseArgs(args As String, obj As RawObject)
        If mArguments IsNot Nothing Then
            mArguments.Clear()
        Else
            mArguments = New List(Of String)
        End If

        If args <> "" Then

            Dim parts As New List(Of String)(args.Split(":"c))
            Dim argHandler As IArgHandler = Nothing
            Dim usage As TokenUsage = Nothing
            If obj IsNot Nothing Then
                usage = TokenDictionary.Instance.GetUsage(Me.Name, obj.ObjectType, obj.ObjectSubType)
                If usage IsNot Nothing Then
                    Dim argsHandler As IArgsTypeHandler = ArgumentManager.instance.GetArgsTypeHandler(usage.ArgsType)

                    'for tokens with a defined usage
                    Dim argIndex As Integer = 0
                    Dim argPartEnumerator As IEnumerator(Of String) = parts.GetEnumerator
                    'process all parts of the token by looping through them and passing them to the proper argument handler for parsing
                    Do While argPartEnumerator.MoveNext
                        Dim arg As ArgumentDefinition = Nothing
                        'make sure we are still on a defined argument
                        If argIndex < usage.Arguments.Count Then
                            'on a defined argument we use the actual type
                            arg = usage.Arguments(argIndex)
                        Else
                            If argsHandler.HasDynamicArguments Then
                                arg = argsHandler.GetArgument(argIndex, usage)
                                'argHandler = ArgumentManager.instance.getArgTypeHandler(arg.type)
                            Else
                                'if we went past the end of the defined argument, treat them as strings
                                'argHandler = ArgumentManager.instance.getArgTypeHandler(ArgType.string)
                                arg = New ArgumentDefinition
                                arg.Type = ArgType.String
                            End If
                        End If

                        argHandler = ArgumentManager.instance.GetArgTypeHandler(arg.Type)

                        'use the handler to parse out the correct number of argument parts for the argument
                        mArguments.Add(argHandler.Parse(argPartEnumerator, arg))

                        argIndex += 1
                    Loop
                Else
                    'for tokens without a defined usage. just treat them all as strings
                    'Stop
                    mArguments.AddRange(parts)
                End If
            Else
                'if we don't know what object type it is, treat them as strings
                'Stop
                mArguments.AddRange(parts)
            End If
        End If
    End Sub


    Public Function GetCopy() As Token
        Dim ret As New Token

        ret.Name = Me.Name
        If Me.mArguments IsNot Nothing Then
            ret.mArguments = New List(Of String)(Me.mArguments)
        End If
        ret.Comment = Me.Comment

        Return ret
    End Function

    Public ReadOnly Property Arguments As IList(Of String)
        Get
            Return mArguments
        End Get
    End Property
End Class
